在前面的範例中,我們的測試檔案都符合我們在lex中所定義的規則。然而,要是檔案中有出現預料之外的字串時,lex在無法判斷的狀態下,可能就會導致程式直接crash的狀況。那麼,有甚麼辦法防止這種事情發生呢?
最簡單的方法就是使用.
這個語法。在Day2所做的範例”DNA字母統計”,我們便是利用這個語法來處理除了ATCG四個字母以外的其他字母。然而,當DNA序列出現其他字母時,程式依然繼續進行,最後統計出每種鹼基的數量。
那麼,有沒有辦法在讀到異常字母時,直接跳出讀取的環節,並印出錯誤的訊息?
在Lex & Yacc中,有個現成的error function可用,叫做yyerror()
。
給定一段DNA核酸序列(由A, T, C, G組成),試著計算各個字母(含氮鹼基)的數量。
若是有出現其他字母,則印出錯誤訊息,並終止parser。
除了統計變數外,我們額外紀錄行數,作為錯誤回報使用。
另外,yyerror需要在這裡先宣告,在Rule區塊才能使用。
%{
int numA, numT, numC, numG;
int lineno = 1;
void yyerror(const char *s);
%}
當遇到換行符號時,行數+1。
若是檔案中出現其他字母時,我們改為呼叫yyerror()
。
另外,我們使用預定義函式yyterminate()
直接中止yylex()。
%%
A { numA++; }
T { numT++; }
C { numC++; }
G { numG++; }
\n { lineno++; }
. { yyerror("invalid character"); yyterminate(); }
除了主程式與yywrap(),我們將yyerror()
寫在這裡,將遇到的錯誤種類、內容與行數印出。
%%
void yyerror(const char *s) {
printf("line %d: %s at %s\n", lineno, s, yytext);
}
int yywrap(void) {
printf("Counter :\nA: %d\nT: %d\nC: %d\nG: %d\n", numA, numT, numC, numG);
return 1;
}
int main(void) {
const char* sFile = "file.txt";
FILE* fp = fopen(sFile, "r");
if (fp == NULL) {
printf("cannot open %s\n", sFile);
return -1;
}
yyin = fp;
yylex();
return 0;
}
跟之前不同的是,我們把最終統計的結果放在yywrap裡。當遇到錯誤提前中止時,便不會呼叫yywrap,因此符合我們的需求。
以下的內容在lex檔案中,檔案命名為lex.l。
%{
int numA, numT, numC, numG;
int lineno = 1;
void yyerror(const char *s);
%}
%%
A { numA++; }
T { numT++; }
C { numC++; }
G { numG++; }
\n { lineno++; }
. { yyerror("invalid character"); yyterminate(); }
%%
void yyerror(const char *s) {
printf("line %d: %s at %s\n", lineno, s, yytext);
}
int yywrap(void) {
printf("Counter :\nA: %d\nT: %d\nC: %d\nG: %d\n", numA, numT, numC, numG);
return 1;
}
int main(void) {
const char* sFile = "file.txt";
FILE* fp = fopen(sFile, "r");
if (fp == NULL) {
printf("cannot open %s\n", sFile);
return -1;
}
yyin = fp;
yylex();
return 0;
}
AAAAACCCCCAAAAACCCCCCAAAAAGGGUUUGGGGTTTTTTT
line 1: invalid character at U
有了Error Handling,就可以避免程式跑到一半就crash,或是造成讀取錯誤內容。
至此,Lex的基本介紹就告一段落了。我們接下來就要進入Yacc的部分。
關於lex and yacc中的預定義變數整理於下表:
變數名稱 | 敘述 |
---|---|
int yylex(void) | 呼叫lexer |
char *yytext | 指向匹配字串的指標 |
yyleng | 匹配字串的長度 |
yylval | 與標記相對應的值 |
int yywrap(void) | 總結函數,完成掃描回傳1,反之回傳0 |
FILE *yyout | 輸出檔案 |
FILE *yyin | 輸入檔案 |
INITIAL | 起始狀態 |
BEGIN | 切換至特定條件 |
ECHO | 輸出匹配的字串 |